import os, re, winreg, wx
import api, config, core, gui, tones, ui, winUser
from contentRecog import LinesWordsResult, recogUi, RecogImageInfo
from ..wrapper import subthread
from ..ocr import getOcrRecognizer
from .. import constants, utils
SELF_DIR = os.path.dirname(__file__)
ANDROID_DIR = os.path.join(os.environ['USERPROFILE'], '.android')
if not os.path.exists(ANDROID_DIR): # 如果没有安卓目录，就创建
    os.mkdir(ANDROID_DIR)
ADB_EXE = os.path.join(SELF_DIR, 'adb.exe') # adb.exe 所在路径
SCREEN_PNG = os.path.join(SELF_DIR, 'screen.png') # 屏幕截图保存的位置
def getAdbDevices(): # 获取手机 USB 设备列表
    def getSubkey(key, name): # 注册表打开一个子键
        try:
            with winreg.OpenKey(key, name) as subkey:
                j = winreg.EnumKey(subkey, 0)
                with winreg.OpenKey(subkey, j) as subkey2:
                    return j, winreg.QueryValueEx(subkey2, 'DeviceDesc')[0]
        except:pass
    # 从注册表信息中读取 USB 设备
    with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SYSTEM\CurrentControlSet\Enum\USB') as key:
        devices = []
        i = 0
        while True:
            try:
                j = winreg.EnumKey(key, i)
            except:break
            subkey = getSubkey(key, j)
            # 寻找 adb.exe 支持的设备
            if subkey and subkey[1].upper().count('ADB DEVICE'):
                devices.append(j)
            i += 1
        return devices
def adbPatch(): # 解决 adb.exe 找不到某些设备的补丁
    devices = getAdbDevices()
    if not devices:return
    vids = re.findall('VID_([A-F0-9]{4})', ','.join(devices), flags = re.I)
    if not vids:return
    # 将设备列表写入配置
    with open(os.path.join(ANDROID_DIR, 'adb_usb.ini'), 'w', encoding = constants.CHARSET) as f:
        for vid in vids:
            f.write(f'0x{vid}\n')
@subthread # 杀死 adb.exe 进程服务
def killServer():
    utils.system(f'{ADB_EXE} kill-server')
@subthread # 无线连接 adb 设备
def connect():
    # 读取用户的设备 IP
    deviceIp = config.conf[constants.ADB]['device']
    if not deviceIp.strip():return
    utils.system(f'{ADB_EXE} tcpip 5555')
    utils.system(f'{ADB_EXE} connect {deviceIp}')
@subthread
def forward(localPort, remotePort, callback):
    utils.system(f'{ADB_EXE} forward tcp:{localPort} tcp:{remotePort}')
    callback()
adbPatch() # 打补丁
# 重写的 NVDA 识别结果类
class AdbRecogResultNVDAObject(recogUi.RecogResultNVDAObject):
    # 处理点击事件
    def script_activatePosition(self, gesture):
        # 点击所对应手机截图的坐标
        p = self._selection.pointAtStart
        # 点击手机屏幕
        utils.system(f'{ADB_EXE} shell input tap {p.x} {p.y}')
        tones.beep(500, 100)
@subthread # 截图任务需要运行在子线程中
def screenshot():
    # 如果当前焦点是识别结果，则跳过
    if isinstance(api.getFocusObject(), AdbRecogResultNVDAObject):
        ui.message(_("Already in a content recognition result"))
        return
    tones.beep(270, 300)
    # 第一步：手机截图
    if os.path.exists(SCREEN_PNG):
        os.unlink(SCREEN_PNG)
    utils.system(f'{ADB_EXE} shell screencap -p /sdcard/s.png')
    tones.beep(540, 200)
    # 第二部：把图片从手机拉到本地电脑
    utils.system(f'{ADB_EXE} pull /sdcard/s.png {SCREEN_PNG}')
    if not os.path.exists(SCREEN_PNG):
        ui.message('截图失败！')
        return
    tones.beep(1080, 100)
    # 第三部：准备识别
    ui.message(_("Recognizing"))
    try:
        ocrRecognizer = getOcrRecognizer()
        with open(SCREEN_PNG, 'rb') as f: # 读取图片文件
            f.seek(16) # 跳到 PNG 文件的第 16 个字节位置
            width = int.from_bytes(f.read(4), byteorder='big') # 宽度
            height = int.from_bytes(f.read(4), byteorder='big') # 高度
            f.seek(0) # 读取完图片的宽高就回到文件开头
            ret = ocrRecognizer.onRecog(f.read()) # 开始识别
        tones.beep(2160, 50)
        # NVDA 抽象的识别结果需要图片信息，我们给她补充完整
        imgInfo = RecogImageInfo.createFromRecognizer(0, 0, width, height, ocrRecognizer)
        # 生成 NVDA 约定的识别结果信息
        result = LinesWordsResult(ret, imgInfo)
        # 可以用光标浏览的识别结果文档对象
        obj = AdbRecogResultNVDAObject(result = result)
        obj.setFocus()
    except Exception as e:
        ui.message(_('%s' % e))
@subthread # 截图任务需要运行在子线程中
def screenshotToClipboard():
    # 第一步：手机截图
    if os.path.exists(SCREEN_PNG):
        os.unlink(SCREEN_PNG)
    utils.system(f'{ADB_EXE} shell screencap -p /sdcard/s.png')
    tones.beep(270, 100)
    # 第二部：把图片从手机拉到本地电脑
    utils.system(f'{ADB_EXE} pull /sdcard/s.png {SCREEN_PNG}')
    if not os.path.exists(SCREEN_PNG):
        ui.message('截图失败！')
        return
    tones.beep(540, 50)
    # 第三部：复制到剪贴板
    core.callLater(1, copyFileToClipboard, SCREEN_PNG)
def copyFileToClipboard(filename):
    file = wx.FileDataObject()
    file.AddFile(filename)
    with wx.TheClipboard as c:
        c.SetData (file)
def installApp ():
    # 安装手机APK，实验性功能，如果APK文件名是中文可能会失败
    def inner():
        wildcard = 'Android Package (apk file)|*.apk'
        dlg = wx.FileDialog(gui.mainFrame, message = '请选择安装包', defaultDir = '', defaultFile = "", wildcard = wildcard, style = wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_FILE_MUST_EXIST)
        if dlg.ShowModal() != wx.ID_OK:
            dlg.Destroy()
            return
        installApk(dlg.GetPath())
        dlg.Destroy()
    wx.CallAfter(inner)
@subthread
def installApk(filename, callback = None):
    # 开始安装APK
    utils.system(f'{ADB_EXE} install {filename}')
    tones.beep(660, 50)
    if callback:
        callback()
@subthread
def shell(cmd):
    # 执行shell命令
    utils.system(f'{ADB_EXE} shell {cmd}')
    tones.beep(660, 50)
